﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using DXBase;

namespace DXBase.Structs
{
	/// <summary>
	/// A picking ray.
	/// </summary>
	public struct Ray
	{
		public Vector3F Origin;
		public Vector3F Direction;

		public Vector3F PointAt(float dist) { return Origin + dist * Direction; }

		public static Ray operator*(Matrix4x4F m, Ray r)
		{
			float s = (m.m00 + m.m11 + m.m22) / 3;
			return new Ray
			{
				Direction = m.ConvertVector(r.Direction),
				Origin = m.ConvertPoint(r.Origin),
			};
		}

		public bool IntersectBox(Box3D box)
		{
			float dist;
			return IntersectBox(box, out dist);
		}
		public bool IntersectBox(Box3D box, out float dist)
		{
			dist = float.NaN;
			float txMin, txMax, tyMin, tyMax, tzMin, tzMax;
			if (
				!IntersectSegment(box.X, box.XMax, Origin.X, Direction.X, out txMin, out txMax)
				||!IntersectSegment(box.Y, box.YMax, Origin.Y, Direction.Y, out tyMin, out tyMax)
				||!IntersectSegment(box.Z, box.ZMax, Origin.Z, Direction.Z, out tzMin, out tzMax)
				)
				return false;
			float min = Math.Max(Math.Max(txMin, tyMin), tzMin);
			float max = Math.Min(Math.Min(txMax, tyMax), tzMax);
			if (min <= max && min >= 0)
			{
				dist = min;
				return true;
			}
			dist = float.NaN;
			return false;
		}
		static bool IntersectSegment(float min, float max, float o, float dp, out float tMin, out float tMax)
		{
			if (Math.Abs(dp) <= float.Epsilon)
			{
				if (min <= o && o <= max)
				{
					tMin = 0;
					tMax = float.PositiveInfinity;
					return true;
				}
			}
			else
			{
				if (dp > 0)
				{
					tMin = (min - o) / dp;
					tMax = (max - o) / dp;
				}
				else
				{
					tMin = (max - o) / dp;
					tMax = (min - o) / dp;
				}
				return true;
			}
			tMin = float.NaN;
			tMax = float.NaN;
			return false;
		}

		public bool IntersectSphere(Vector3F p, float radius)
		{
			float dist;
			return IntersectSphere(p, radius, out dist);
		}
		public bool IntersectSphere(Vector3F p, float radius, out float dist)
		{
			if (radius <= 0)
			{
				dist = float.NaN;
				return false;
			}
			var qc = Origin - p;
			float tmp = Direction * Direction;
			if (tmp <= float.Epsilon)
			{
				dist = float.NaN;
				return false;
			}
			float a = (2 * Direction * qc) / tmp;
			float b = (qc * qc - radius * radius) / tmp;

			float squared = a * a - 4 * b;
			if (squared < 0)
			{
				dist = float.NaN;
				return false;
			}

			float sqrt = squared.Sqrtf();
			float min = (-a - sqrt) / 2;
			float max = (-a + sqrt) / 2;
			if (max < 0)
			{
				dist = float.NaN;
				return false;
			}

			dist = Math.Max(min, 0);
			return true;
		}

		public bool IntersectTriangle(Triangle3D triangle, out float dist)
		{
			// 1st find intersection between Ray center line and p0,1,2 triangle's plane
			var e1 = triangle.P1 - triangle.P0;
			var e2 = triangle.P2 - triangle.P0;
			var m = Origin - triangle.P0;
			var u_e2 = Direction.Cross(e2);
			var div = e1 * u_e2;
			if (Math.Abs(div) < 0.000001)
			{
				// Triangle is parallel to ray, do sphere pick!
				dist = float.NaN;
				return false;
			}
			var m_e1 = m.Cross(e1);
			var t = (e2 * m_e1) / div;
			var u = (m * u_e2) / div;
			var v = (Direction * m_e1) / div;

			// simple ray pick
			if (u >= 0 && v >= 0 && u + v <= 1 && t >= 0)
			{
				dist = t;
				return true;
			}
			else
			{
				dist = float.NaN;
				return false;
			}
			// TODO: cone pick!
		}
		public HitMeshResult IntersectMesh(IEnumerable<Triangle3D> mesh)
		{
			bool touch = false;
			float dist = float.NaN;
			int index = -1;
			int tindex = -1;
			foreach (var triangle in mesh)
			{
				tindex++;
				float adist;
				if (IntersectTriangle(triangle, out adist))
				{
					if (!touch || adist < dist)
					{
						touch = true;
						dist = adist;
						index = tindex;
					}
				}
			}
			return new HitMeshResult
			{
				Hit = touch,
				Distance = dist,
				Triangle = index,
			};
		}
		public struct HitMeshResult
		{
			public bool Hit;
			public float Distance;
			public int Triangle;
		}
	}
}
